/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.sd.chat;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.Vector;
import javax.imageio.ImageIO;

/**
 *
 * @author Old
 */
public class ServerManager extends Thread {

    private ServerData db;
    private Map<UUID, ClientHandlerInterface> ligacoes;
    private Vector<MsgReceived> receivedQueue;
    private boolean shutdown = false;
    private Vector<String> serverLog;
    private boolean shoudManage = false;
    private Logger log = new Logger(true);
    private ChatServerView serverView;
    private FBManager fbmanager;

    public ServerManager(ServerData db, ChatServerView serverV) {
        serverLog = new Vector<String>();
        receivedQueue = new Vector<MsgReceived>();
        ligacoes = Collections.synchronizedMap(new LinkedHashMap<UUID, ClientHandlerInterface>());
        this.serverView = serverV;
        this.db = db;
        fbmanager = new FBManager(this);
        fbmanager.start();
    }

    @Override
    public void run() {
        while (!isShutdown()) {
            manageServer();
        }
    }

    public synchronized void manageServer() {
        //while (!shoudManage) {

            if (!receivedQueue.isEmpty()) {
                MsgReceived msgTemp = receivedQueue.remove(0);
                MsgType oTipo = msgTemp.msg.type;
                if (oTipo == MsgType.LOGIN) {

                    Utilizador userAtempt = (Utilizador) getObjectFromBytes(msgTemp.msg.content);
                    ClientHandlerInterface tempCli = ligacoes.get(msgTemp.from);
                    tryLoginTCP(userAtempt, tempCli);

                } else if (oTipo == MsgType.REGISTER) {

                    Utilizador userAtempt = (Utilizador) getObjectFromBytes(msgTemp.msg.content);
                    ClientHandlerInterface tempCli = ligacoes.get(msgTemp.from);
                    tryRegisterTCP(userAtempt, tempCli);

                } else if (oTipo == MsgType.POST) {

                    Post tempPost = (Post) getObjectFromBytes(msgTemp.msg.content);
                    ClientHandlerInterface tempCli = ligacoes.get(msgTemp.from);
                    tempPost.setUser(tempCli.getLoggedUser());

                    newPost(tempPost);

                } else if (oTipo == MsgType.COMMENT) {
                    Comment tempCom = (Comment) getObjectFromBytes(msgTemp.msg.content);
                    ClientHandlerInterface tempCli = ligacoes.get(msgTemp.from);
                    tempCom.setUser(tempCli.getLoggedUser());

                    newComment(tempCom);

                    //Collections.sort(db.getComments());
                } else if (oTipo == MsgType.EDIT) {

                    Post tempPost = (Post) getObjectFromBytes(msgTemp.msg.content);
                    ClientHandlerInterface tempCli = ligacoes.get(msgTemp.from);
                    tempPost.setUser(tempCli.getLoggedUser());

                    editPost(tempPost);

                } else if (oTipo == MsgType.MESSAGE) {
                    Message tempMess = (Message) getObjectFromBytes(msgTemp.msg.content);

                    ClientHandlerInterface tempCli = ligacoes.get(msgTemp.from);
                    tempMess.setFrom(tempCli.getLoggedUser());

                    newMessage(tempMess);

                    //Collections.sort(db.getPms());
                } else if (oTipo == MsgType.ELIMINATEPOST) {

                    Integer tempId = (Integer) getObjectFromBytes(msgTemp.msg.content);
                    for (int i = 0; i < db.getPosts().size(); i++) {
                        if (db.getPosts().get(i).getId() == tempId) {
                            for (int j = 0; j < db.getComments().size(); j++) {
                                if (db.getComments().get(j).getPostId() == tempId) {
                                    db.getComments().remove(j);
                                }
                            }
                            if (db.getPosts().get(i).isHaspic()) {
                                File tempPic = new File(db.getPosts().get(i).getPic());
                                if (!tempPic.delete()) {
                                    tempPic.deleteOnExit();
                                }
                            }
                            Post rempost = db.getPosts().remove(i);
                            if(!rempost.getFbId().equals("")){
                                fbmanager.setDeletePostToFB(rempost);
                            }

                            sendToAllOnline(MsgType.ELIMINATEPOST, tempId);
                            break;
                        }
                    }


                    //Collections.sort(db.getPms());
                } else if (oTipo == MsgType.ELIMINATECOMMENT) {

                    Integer tempId = (Integer) getObjectFromBytes(msgTemp.msg.content);
                    for (int j = 0; j < db.getComments().size(); j++) {
                        if (db.getComments().get(j).getId() == tempId) {
                            Comment remCom = db.getComments().remove(j);
                            if(!remCom.getFbId().equals("")){
                                fbmanager.setDeleteCommentToFB(remCom);
                            }
                            sendToAllOnline(MsgType.ELIMINATECOMMENT, tempId);
                            break;
                        }
                    }




                    //Collections.sort(db.getPms());
                } else if (oTipo == MsgType.PICSEND) {
                    try {
                        PicSent picMsg = (PicSent) getObjectFromBytes(msgTemp.msg.content);
                        BufferedImage image = ImageIO.read(new ByteArrayInputStream(picMsg.picdata));
                        ImageIO.write(image, "jpeg", new File(picMsg.picName));
                        addMessageToLog("ServerManager recebi e gravei Imagem: " + picMsg.picName);
                    } catch (IOException ex) {
                        log.erro("ServerManager erro na imagem que chegou.");
                    }
                }
            }
            long nowTime = new GregorianCalendar().getTime().getTime();
            Vector<Post> readyPosts = new Vector<Post>();
            for (int i = 0; i < this.db.getDelayedposts().size(); i++) {
                if (this.db.getDelayedposts().get(i).getData().getTime().getTime() < nowTime) {
                    readyPosts.add(this.db.getDelayedposts().remove(i));
                    i--;
                }
            }
            if (readyPosts.size() > 0) {
                Collections.sort(readyPosts);
                for (int i = 0; i < readyPosts.size(); i++) {
                    if (readyPosts.get(i).isHaspic()) {
                        try {
                            File picFile = new File(readyPosts.get(i).getPic());
                            String picName = picFile.getName();
                            BufferedImage pic = ImageIO.read(picFile);

                            ByteArrayOutputStream baos = new ByteArrayOutputStream();
                            ImageIO.write(pic, "jpeg", baos);
                            baos.flush();
                            byte[] picInfo = baos.toByteArray();
                            baos.close();
                            PicSent picMsg = new PicSent(picInfo, picName);
                            sendToAllOnline(MsgType.PICSEND, picMsg);
                        } catch (IOException ex) {
                            log.erro("ServerManager erro a tentar enviar foto");
                        }
                    }
                    sendToAllOnline(MsgType.POST, readyPosts.get(i));
                }
                this.db.getPosts().addAll(readyPosts);
            }
            shoudManage = false;
        //}
            if (receivedQueue.isEmpty()) {
                try {
                    log.outln("ServerManager manage preparing to wait");
                    wait(1000);
                } catch (InterruptedException e) {
                    log.erro("interruptedException caught");
                }
                log.outln("ServerManager resuming");
            }

    }

    public synchronized void sendToAllOnline(MsgType tipo, Serializable mensagem) {
        byte[] tempPBytes = getBytes(mensagem);
        TCPMsg temPmsg = new TCPMsg(tipo, tempPBytes.length, tempPBytes);
        ArrayList<ClientHandlerInterface> allOnline = new ArrayList<ClientHandlerInterface>();
        allOnline.addAll(ligacoes.values());
        for (int i = 0; i < allOnline.size(); i++) {
            if (allOnline.get(i).isLogged()) {
                allOnline.get(i).addMsg(temPmsg);
                allOnline.get(i).getLoggedUser().setLastPost(new Date());
            }
        }
    }

    public synchronized void sendToOne(MsgType tipo, Serializable mensagem, ClientHandlerInterface tempCli) {
        byte[] tempPBytes = getBytes(mensagem);
        TCPMsg temPmsg = new TCPMsg(tipo, tempPBytes.length, tempPBytes);
        tempCli.addMsg(temPmsg);
    }

    public synchronized void tryLoginTCP(Utilizador userAtempt, ClientHandlerInterface tempCli) {
        boolean loginSucess = false;
        for (int i = 0; i < db.getUsers().size(); i++) {
            Utilizador compareUser = db.getUsers().get(i);
            if (compareUser.getNome().equals(userAtempt.getNome()) && compareUser.getPasse().equals(userAtempt.getPasse())) {
                loginSucess = true;
                userAtempt = compareUser;
            }
        }

        if (loginSucess) {
            tempCli.setLoggedUser(userAtempt);
            tempCli.setLogged(true);

            sendToOne(MsgType.LOGINACCEPT, userAtempt, tempCli);

            addMessageToLog("User loggedin: " + userAtempt.getNome());

            sendOnlineUsers();

            updateUser(tempCli);
        } else {
            String replyFailed = "Nome de utilizador ou password invélidos.";

            sendToOne(MsgType.LOGINFAILED, replyFailed, tempCli);

            addMessageToLog("User failed log in");
        }
    }

    public synchronized void updateUser(ClientHandlerInterface user) {
        int userId = user.getLoggedUser().getId();
        Date userLastPM = user.getLoggedUser().getLastPM();
        int i = this.db.getPms().size() - 1;
        if (this.db.getPms().size() > 0) {
            while (i >= 0 && this.db.getPms().get(i).getData().getTime().getTime() - userLastPM.getTime() > 0) {
                if (this.db.getPms().get(i).getTo().contains(userId)) {
                    sendToOne(MsgType.MESSAGE, this.db.getPms().get(i), user);
                }
                i--;
            }
        }
        user.getLoggedUser().setLastPM(new Date());

        Date userLastPost = user.getLoggedUser().getLastPost();
        log.outln("Updating user -> userLastPost: " + userLastPost);
        i = this.db.getPosts().size() - 1;
        log.outln("Updating user -> this.db.getPosts().size(): " + this.db.getPosts().size());
        if (this.db.getPosts().size() > 0) {
            log.outln("Updating user -> this.db.getPosts().get(i).getData().getTime(): " + this.db.getPosts().get(i).getData().getTime());
            log.outln("Updating user -> subtract: " + (this.db.getPosts().get(i).getData().getTime().getTime() - userLastPost.getTime()));

            while (i >= 0 && this.db.getPosts().get(i).getData().getTime().getTime() - userLastPost.getTime() > 0) {
                if (this.db.getPosts().get(i).isHaspic()) {
                    try {
                        File picFile = new File(this.db.getPosts().get(i).getPic());
                        String picName = picFile.getName();
                        BufferedImage pic = ImageIO.read(picFile);

                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        ImageIO.write(pic, "jpeg", baos);
                        baos.flush();
                        byte[] picInfo = baos.toByteArray();
                        baos.close();
                        PicSent picMsg = new PicSent(picInfo, picName);
                        sendToOne(MsgType.PICSEND, picMsg, user);
                    } catch (IOException ex) {
                        log.erro("ServerManager erro a tentar enviar foto");
                    }
                }
                log.outln("Updating user -> Post: " + this.db.getPosts().get(i));
                sendToOne(MsgType.POST, this.db.getPosts().get(i), user);

                i--;
            }
        }
        user.getLoggedUser().setLastPost(new Date());
    }

    public synchronized void tryRegisterTCP(Utilizador userAtempt, ClientHandlerInterface tempCli) {
        boolean canRegister = true;
        for (int i = 0; i < db.getUsers().size(); i++) {
            Utilizador compareUser = db.getUsers().get(i);
            if (compareUser.getNome().equals(userAtempt.getNome())) {
                canRegister = false;
            }
        }

        if (canRegister) {
            userAtempt.setId(db.getUserID());
            userAtempt.setLastPost(new Date());
            userAtempt.setLastPM(new Date());
            tempCli.setLoggedUser(userAtempt);
            tempCli.setLogged(true);
            db.setUserID(db.getUserID() + 1);
            db.getUsers().add(userAtempt);

            sendToOne(MsgType.REGISTERACCEPT, userAtempt, tempCli);

            addMessageToLog("User registered: " + userAtempt);

            sendOnlineUsers();
        } else {
            String replyFailed = "Nome de utilizador já existe.";

            sendToOne(MsgType.REGISTERFAILED, replyFailed, tempCli);

            addMessageToLog("User register failed");
        }
    }

    public synchronized void newMessage(Message tempMess) {
        tempMess.setId(db.getPmID());
        db.setPmID(db.getPmID() + 1);

        db.getPms().add(tempMess);

        ArrayList<ClientHandlerInterface> allOnline = new ArrayList<ClientHandlerInterface>();
        allOnline.addAll(ligacoes.values());
        byte[] tempPBytes = getBytes(tempMess);
        TCPMsg temPmsg = new TCPMsg(MsgType.MESSAGE, tempPBytes.length, tempPBytes);
        for (int i = 0; i < allOnline.size(); i++) {
            if (allOnline.get(i).isLogged() && tempMess.getTo().contains((Integer) allOnline.get(i).getLoggedUser().getId())) {
                allOnline.get(i).addMsg(temPmsg);
                allOnline.get(i).getLoggedUser().setLastPM(new Date());
            }
        }
    }

    public synchronized void newComment(Comment tempCom) {
        tempCom.setId(db.getComentID());
        db.setComentID(db.getComentID() + 1);
        db.getComments().add(tempCom);
        
        for (int i = 0; i < db.getPosts().size(); i++) {
            if (db.getPosts().get(i).getId() == tempCom.getPostId()) {
                fbmanager.setAddCommentToFB(db.getPosts().get(i), tempCom);
                break;
            }
        }

        sendToAllOnline(MsgType.COMMENT, tempCom);
    }

    public synchronized void editPost(Post tempPost) {
        for (int i = 0; i < db.getPosts().size(); i++) {
            if (db.getPosts().get(i).getId() == tempPost.getId()) {
                db.getPosts().get(i).setText(tempPost.getText());
                db.getPosts().get(i).setData(new GregorianCalendar());
                sendToAllOnline(MsgType.POST, db.getPosts().get(i));
                return;
            }
        }
    }

    public synchronized void newPost(Post tempPost) {
        tempPost.setId(db.getPostID());
        db.setPostID(db.getPostID() + 1);
        GregorianCalendar agoraData = new GregorianCalendar();
        if (tempPost.getData().compareTo(agoraData) <= 0) {
            db.getPosts().add(tempPost);
            
            if(tempPost.getFbId().equals("")){
                fbmanager.setAddPostToFB(tempPost);
            }
            //Collections.sort(db.getPosts());
            ArrayList<ClientHandlerInterface> allOnline = new ArrayList<ClientHandlerInterface>();
            allOnline.addAll(ligacoes.values());


            byte[] tempPBytes = getBytes(tempPost);
            TCPMsg temPmsg = new TCPMsg(MsgType.POST, tempPBytes.length, tempPBytes);
            TCPMsg msg = new TCPMsg(MsgType.CONACCEPT, 0, new byte[0]);
            if (tempPost.isHaspic()) {
                try {
                    File picFile = new File(tempPost.getPic());
                    String picName = picFile.getName();
                    BufferedImage pic = ImageIO.read(picFile);

                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    ImageIO.write(pic, "jpeg", baos);
                    baos.flush();
                    byte[] picInfo = baos.toByteArray();
                    baos.close();
                    PicSent picMsg = new PicSent(picInfo, picName);
                    byte[] picMsgBytes = getBytes(picMsg);
                    msg = new TCPMsg(MsgType.PICSEND, picMsgBytes.length, picMsgBytes);
                } catch (IOException ex) {
                    log.erro("ServerManager erro a tentar enviar foto");
                }
            }


            for (int i = 0; i < allOnline.size(); i++) {

                if (tempPost.isHaspic()) {
                    allOnline.get(i).addMsg(msg);
                }

                allOnline.get(i).addMsg(temPmsg);

                allOnline.get(i).getLoggedUser().setLastPost(new Date());
            }
            
        } else {
            db.getDelayedposts().add(tempPost);
            Collections.sort(db.getPosts());
        }
    }

    public synchronized void newConnection(UUID uId, ClientHandler newCon) {
        ligacoes.put(uId, newCon);
        //newCon.addMsg(new TCPMsg(MsgType.CONACCEPT, 0, new byte[0]));
        newCon.start();
        log.outln("new connection received: " + uId);
        addMessageToLog("new connection received");
    }
    
    public synchronized void newRMIConnection(UUID uId, ClientHandlerInterface newCon) {
        ligacoes.put(uId, newCon);
        log.outln("new connection received: " + uId);
        addMessageToLog("new connection received");
    }

    public synchronized void newMessageReceived(UUID uId, TCPMsg newMsg) {
        receivedQueue.add(new MsgReceived(uId, newMsg));
        shoudManage = true;
        notifyAll();
        log.outln("ServerManager MsgRcv: " + newMsg);
        addMessageToLog("ServerManager MsgRcv: " + newMsg);
    }

    public synchronized void connectionLost(UUID uId) {
        ClientHandlerInterface tempHandler = ligacoes.get(uId);
        tempHandler.setLogged(false);
        log.outln("ServerManager ClientDisconnect: " + uId);
        addMessageToLog("ServerManager ClientDisconnect: " + uId);
        ligacoes.remove(uId);
        sendOnlineUsers();

    }

    public synchronized void sendOnlineUsers() {
        ArrayList<ClientHandlerInterface> allOnline = new ArrayList<ClientHandlerInterface>();
        allOnline.addAll(ligacoes.values());
        UserList tempList = new UserList();
        for (int i = 0; i < allOnline.size(); i++) {
            if (allOnline.get(i).isLogged()) {
                tempList.addUser(allOnline.get(i).getLoggedUser().getNome(), allOnline.get(i).getLoggedUser().getId());
            }
        }
        byte[] tempPBytes = getBytes(tempList);
        TCPMsg temPmsg = new TCPMsg(MsgType.ONLINEUSERS, tempPBytes.length, tempPBytes);
        for (int i = 0; i < allOnline.size(); i++) {
            if (allOnline.get(i).isLogged()) {
                allOnline.get(i).addMsg(temPmsg);
            }
        }
    }

    public synchronized void shutdown() {
        this.setShutdown(true);
    }

    public boolean isShutdown() {
        return shutdown;
    }

    public void setShutdown(boolean shutdown) {
        this.shutdown = shutdown;
    }

    public Vector<String> getServerLog() {
        return serverLog;
    }

    public void setServerLog(Vector<String> serverLog) {
        this.serverLog = serverLog;
    }

    public synchronized void addMessageToLog(String msg) {
        this.serverLog.add(msg);
        notifyAll();
    }

    public synchronized boolean hasMessagesToDisplay() {
        if (this.serverLog.size() > 0) {
            return true;
        }
        return false;
    }

    public synchronized String getMessageToDisplay() {
        return this.serverLog.remove(0);
    }

    public synchronized void dispatchToGUI() {
        while (!this.hasMessagesToDisplay()) {
            try {
                wait();
            } catch (InterruptedException e) {
                log.erro("interruptedException caught");
            }
            while (this.hasMessagesToDisplay()) {
                serverView.displayMsg(this.getMessageToDisplay());
            }
        }
    }
    
    public synchronized void setFBStatus(String status){
        serverView.setFaceStatus(status);
    }
    
    public synchronized void setFBCode(String code){
        fbmanager.setCodeFromWeb(code);
    }
    
    public synchronized void setFBURL(String url){
        serverView.setFaceCodeUrl(url);
    }
    
    public synchronized void setPostFBID(int postId, String faceId){
        for (int i = 0; i < db.getPosts().size(); i++) {
            if (db.getPosts().get(i).getId() == postId) {
                db.getPosts().get(i).setFbId(faceId);
                return;
            }
        }
    }
    
    public synchronized void setCommentFBID(int comId, String faceId){
        for (int i = 0; i < db.getComments().size(); i++) {
            if (db.getComments().get(i).getId() == comId) {
                db.getComments().get(i).setFbId(faceId);
                return;
            }
        }
    }

    public byte[] getBytes(Serializable obj) {
        byte[] yourBytes;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutput out = null;
        try {
            out = new ObjectOutputStream(bos);
            out.writeObject(obj);
            yourBytes = bos.toByteArray();
            out.close();
            bos.close();
            return yourBytes;
        } catch (IOException e) {
        }
        return new byte[0];
    }

    public Object getObjectFromBytes(byte[] yourBytes) {
        Object retorno;
        ByteArrayInputStream bis = new ByteArrayInputStream(yourBytes);
        ObjectInput in = null;
        try {
            in = new ObjectInputStream(bis);
            retorno = in.readObject();
            bis.close();
            in.close();
            return retorno;
        } catch (ClassNotFoundException e) {
        } catch (IOException e) {
        }

        return new Object();
    }

    public synchronized ServerData getDb() {
        return db;
    }

    public synchronized void setDb(ServerData db) {
        this.db = db;
    }
    
    
}
